﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Generic;

namespace SernaJson
{
    public static class ExpressionBasedReflection
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Func<object> GetInstanceCreator(Type type)
        {
            //http://geekswithblogs.net/mrsteve/archive/2012/02/19/a-fast-c-sharp-extension-method-using-expression-trees-create-instance-from-type-again.aspx

            var constructorCallExpression = Expression.New(type);
            //var constructorCallingLambda = Expression
            //    .Lambda<Func<object>>(constructorCallExpression)
            //    .Compile();
            //return constructorCallingLambda;
            return Expression
                .Lambda<Func<object>>(constructorCallExpression)
                .Compile();
        }

        public static Action<object, object> GetFieldSetter(FieldInfo field)
        {
            ParameterExpression instance = Expression.Parameter(typeof(object));
            ParameterExpression argument = Expression.Parameter(typeof(object), field.Name);
            MemberExpression fieldGetterExpression = Expression.Field(Expression.Convert(instance, field.DeclaringType), field);
            BinaryExpression assignExp = Expression.Assign(fieldGetterExpression, Expression.Convert(argument, field.FieldType));
            return Expression.Lambda<Action<object, object>>(assignExp, instance, argument).Compile();
            //var result = Expression.Lambda<Action<object, object>>(assignExp, instance, argument).Compile();
            //return result;
        }

        //public static Func<object, object> GetFieldGetter(FieldInfo field)
        //{
        //    ParameterExpression instance = Expression.Parameter(typeof(object));
        //    Expression fieldGetterExpression = Expression.Field(Expression.Convert(instance, field.DeclaringType), field.Name);
        //    Expression ConvertedfieldGetterExpression = Expression.Convert(fieldGetterExpression, typeof(object));
        //    Func<object, object> result = Expression.Lambda<Func<object, object>>(ConvertedfieldGetterExpression, instance).Compile();
        //    return result;
        //}
        public static Func<object, object> GetFieldGetter(FieldInfo field)
        {
            ParameterExpression instance = Expression.Parameter(typeof(object));
            Expression fieldGetterExpression = Expression.Field(Expression.Convert(instance, field.DeclaringType), field.Name);
            Expression ConvertedfieldGetterExpression = Expression.Convert(fieldGetterExpression, typeof(object));
            return Expression.Lambda<Func<object, object>>(ConvertedfieldGetterExpression, instance).Compile();
            //Func<object, object> result = Expression.Lambda<Func<object, object>>(fieldGetterExpression, instance).Compile();
            //return result;
        }

        //public static Func<object, object> GetPropertyGetter(PropertyInfo propInfo)
        //{
        //    ParameterExpression instance = Expression.Parameter(typeof(object));
        //    Expression propertyGetterExpression = Expression.Property(Expression.Convert(instance, propInfo.DeclaringType), propInfo.Name);
        //    Expression EndPropertyGetterExpression = Expression.Convert(propertyGetterExpression, typeof(object));
        //    Func<object, object> result = Expression.Lambda<Func<object, object>>(EndPropertyGetterExpression, instance).Compile();

        //    return result;
        //}
        public static Func<object, object> GetPropertyGetter(PropertyInfo propInfo)
        {
            ParameterExpression instance = Expression.Parameter(typeof(object));
            Expression propertyGetterExpression = Expression.Property(Expression.Convert(instance, propInfo.DeclaringType), propInfo.Name);            
            Expression EndPropertyGetterExpression = Expression.Convert(propertyGetterExpression, typeof(object));            
            return Expression.Lambda<Func<object, object>>(EndPropertyGetterExpression, instance).Compile();
        }

        public static Action<object, object> GetPropertySetter(PropertyInfo propertyInfo)
        {
            var instance = Expression.Parameter(typeof(object));
            var argument = Expression.Parameter(typeof(object));
            var setterCall = Expression.Call(
                Expression.Convert(instance, propertyInfo.DeclaringType),
                propertyInfo.GetSetMethod(),
                Expression.Convert(argument, propertyInfo.PropertyType));
            return (Action<object, object>)Expression.Lambda(setterCall, instance, argument).Compile();
        }








        /// <summary>
        /// Params to returned action: Instance, Key, Value (which will be added to instance of this generic dictionary)
        /// </summary>
        /// <param name="dictionaryType"></param>
        /// <returns></returns>
        public static Action<object, object, object> SetterMethodAddOfGenericDictionary(Type dictionaryType)
        {
            var instance = Expression.Parameter(typeof(object));
            var argument1 = Expression.Parameter(typeof(object));
            var argument2 = Expression.Parameter(typeof(object));
            System.Reflection.MethodInfo mi = dictionaryType.GetMethod("Add");

            Type[] arguments = dictionaryType.GetGenericArguments();

            var setterCall = Expression.Call(
                Expression.Convert(instance, dictionaryType),
                mi,
                Expression.Convert(argument1, arguments[0]),
                Expression.Convert(argument2, arguments[1])
                );
            return (Action<object, object, object>)Expression.Lambda(setterCall, instance, argument1, argument2).Compile();
        }

        /// <summary>
        /// Params to returned action: Instance,  Value (which will be added to instance of this generic dictionary)
        /// </summary>
        /// <param name="dictionaryType"></param>
        /// <returns></returns>
        public static Action<object, object> SetterMethodAddOfGenericList(Type listType)
        {
            var instance = Expression.Parameter(typeof(object));
            var argument1 = Expression.Parameter(typeof(object));

            System.Reflection.MethodInfo mi = listType.GetMethod("Add");

            Type[] arguments = listType.GetGenericArguments();

            var setterCall = Expression.Call(
                Expression.Convert(instance, listType),
                mi,
                Expression.Convert(argument1, arguments[0])
                );
            return (Action<object, object>)Expression.Lambda(setterCall, instance, argument1).Compile();
        }




        /// <summary>
        /// Params to returned Func: Instance,  Value (which will be added to instance of this generic dictionary), func returns true if value was added to hashset
        /// </summary>
        /// <param name="dictionaryType"></param>
        /// <returns></returns>
        public static Func<object, object, bool> SetterMethodAddOfGenericHashSet(Type hashsetType)
        {
            var instance = Expression.Parameter(typeof(object));
            var argument1 = Expression.Parameter(typeof(object));

            System.Reflection.MethodInfo mi = hashsetType.GetMethod("Add");

            Type[] arguments = hashsetType.GetGenericArguments();

            var setterCall = Expression.Call(
                Expression.Convert(instance, hashsetType),
                mi,
                Expression.Convert(argument1, arguments[0])
                );
            return (Func<object, object, bool>)Expression.Lambda(setterCall, instance, argument1).Compile();
        }




        static Func<Type, int, int, int, object> _gGetInstanceCreatorForArrays3D = null;
        static Func<Type, int, int, object> _gGetInstanceCreatorForArrays2D = null;
        static Func<Type, int, object> _gGetInstanceCreatorForArrays1D = null;

        /// <summary>
        /// Creates 1 dimensional array [,,]
        /// Usage example (no need to cache (already cached)):
        /// var f = WSN_NetCode.Expressions.ExpressionBasedReflection.GetInstanceCreatorForArrays1D();
        /// object tz1 = f(typeof(int), 3);
        /// Result is matrix-array int[3] 
        /// </summary>
        /// <returns></returns>
        public static Func<Type, int, object> GetInstanceCreatorForArrays1D()
        {
            if (_gGetInstanceCreatorForArrays1D != null)
                return _gGetInstanceCreatorForArrays1D;

            var argument1 = Expression.Parameter(typeof(Type));
            var argument2 = Expression.Parameter(typeof(int));

            System.Reflection.MethodInfo mi = new List<System.Reflection.MethodInfo>
              (typeof(Array).GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
              .Where(r => r.ToString().Equals("System.Array CreateInstance(System.Type, Int32)")).FirstOrDefault();

            var setterCall = Expression.Call(
                null,
                mi,
                argument1, argument2
                );

            _gGetInstanceCreatorForArrays1D = (Func<Type, int, object>)Expression.Lambda(setterCall, argument1, argument2).Compile();
            return _gGetInstanceCreatorForArrays1D;
        }

        /// <summary>
        /// Creates 2 dimensional array [,,]
        /// Usage example (no need to cache (already cached)):
        /// var f = WSN_NetCode.Expressions.ExpressionBasedReflection.GetInstanceCreatorForArrays2D();
        /// object tz1 = f(typeof(int), 3, 2 );
        /// Result is matrix-array int[3,2]
        /// </summary>
        /// <returns></returns>
        public static Func<Type, int, int, object> GetInstanceCreatorForArrays2D()
        {
            if (_gGetInstanceCreatorForArrays2D != null)
                return _gGetInstanceCreatorForArrays2D;

            var argument1 = Expression.Parameter(typeof(Type));
            var argument2 = Expression.Parameter(typeof(int));
            var argument3 = Expression.Parameter(typeof(int));

            System.Reflection.MethodInfo mi = new List<System.Reflection.MethodInfo>
              (typeof(Array).GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
              .Where(r => r.ToString().Equals("System.Array CreateInstance(System.Type, Int32, Int32)")).FirstOrDefault();

            var setterCall = Expression.Call(
                null,
                mi,
                argument1, argument2, argument3
                );

            _gGetInstanceCreatorForArrays2D = (Func<Type, int, int, object>)Expression.Lambda(setterCall, argument1, argument2, argument3).Compile();
            return _gGetInstanceCreatorForArrays2D;
        }

        /// Creates 3 dimensional array [,,]
        /// Usage example (no need to cache (already cached)):
        /// var f = WSN_NetCode.Expressions.ExpressionBasedReflection.GetInstanceCreatorForArrays3D();
        /// object tz1 = f(typeof(int), 3, 2, 2 );
        /// Result is matrix-array int[3,2.2]
        public static Func<Type, int, int, int, object> GetInstanceCreatorForArrays3D()
        {
            if (_gGetInstanceCreatorForArrays3D != null)
                return _gGetInstanceCreatorForArrays3D;

            var argument1 = Expression.Parameter(typeof(Type));
            var argument2 = Expression.Parameter(typeof(int));
            var argument3 = Expression.Parameter(typeof(int));
            var argument4 = Expression.Parameter(typeof(int));

            System.Reflection.MethodInfo mi = new List<System.Reflection.MethodInfo>
              (typeof(Array).GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
              .Where(r => r.ToString().Equals("System.Array CreateInstance(System.Type, Int32, Int32, Int32)")).FirstOrDefault();

            var setterCall = Expression.Call(
                null,
                mi,
                argument1, argument2, argument3, argument4
                );

            _gGetInstanceCreatorForArrays3D = (Func<Type, int, int, int, object>)Expression.Lambda(setterCall, argument1, argument2, argument3, argument4).Compile();
            return _gGetInstanceCreatorForArrays3D;
        }



        static Action<object, int, int, int, object> _getValueSetterForArray3D = null;
        static Action<object, int, int, object> _getValueSetterForArray2D = null;
        static Action<object, int, object> _getValueSetterForArray1D = null;

        /// <summary>
        /// Usage example (no need to cache (already cached)):
        /// var f1 = WSN_NetCode.Expressions.ExpressionBasedReflection.GetInstanceCreatorForArrays3D();
        /// object tz2 = f1(typeof(string), 3, 3, 3);
        /// var setup = WSN_NetCode.Expressions.ExpressionBasedReflection.GetSetterForArraysValue3D();
        /// setup(tz2, 1, 1, 1, "kuku");
        /// </summary>
        /// <returns></returns>
        public static Action<object, int, int, int, object> GetValueSetterForArray3D()
        {
            if (_getValueSetterForArray3D != null)
                return _getValueSetterForArray3D;

            var instance = Expression.Parameter(typeof(object));
            var argument1 = Expression.Parameter(typeof(int));
            var argument2 = Expression.Parameter(typeof(int));
            var argument3 = Expression.Parameter(typeof(int));
            var value = Expression.Parameter(typeof(object));

            System.Reflection.MethodInfo mi = new List<System.Reflection.MethodInfo>
              (typeof(Array).GetMethods())
              .Where(r => r.ToString().Equals("Void SetValue(System.Object, Int32, Int32, Int32)")).FirstOrDefault();

            var setterCall = Expression.Call(
                Expression.Convert(instance, typeof(Array)),
                mi,
                value, argument1, argument2, argument3
                //Expression.Convert(value, arrayType),argument1, argument2, argument3
                );

            _getValueSetterForArray3D = (Action<object, int, int, int, object>)Expression.Lambda(setterCall, instance, argument1, argument2, argument3, value).Compile();
            return _getValueSetterForArray3D;
        }

        /// <summary>
        /// Usage example (no need to cache (already cached)):
        /// var f1 = WSN_NetCode.Expressions.ExpressionBasedReflection.GetInstanceCreatorForArrays2D();
        /// object tz2 = f1(typeof(string), 3, 3);
        /// var setup = WSN_NetCode.Expressions.ExpressionBasedReflection.GetSetterForArraysValue2D(typeof(string));
        /// setup(tz2, 1, 1, "kuku");
        /// </summary>
        /// <param name="arrayType"></param>
        /// <returns></returns>
        public static Action<object, int, int, object> GetValueSetterForArray2D()
        {
            if (_getValueSetterForArray2D != null)
                return _getValueSetterForArray2D;

            var instance = Expression.Parameter(typeof(object));
            var argument1 = Expression.Parameter(typeof(int));
            var argument2 = Expression.Parameter(typeof(int));
            var value = Expression.Parameter(typeof(object));

            System.Reflection.MethodInfo mi = new List<System.Reflection.MethodInfo>
              (typeof(Array).GetMethods())
              .Where(r => r.ToString().Equals("Void SetValue(System.Object, Int32, Int32)")).FirstOrDefault();

            var setterCall = Expression.Call(
                Expression.Convert(instance, typeof(Array)),
                mi,
                value, argument1, argument2
                //Expression.Convert(value, arrayType), argument1, argument2
                );

            _getValueSetterForArray2D = (Action<object, int, int, object>)Expression.Lambda(setterCall, instance, argument1, argument2, value).Compile();
            return _getValueSetterForArray2D;
        }


        /// <summary>
        /// Usage example (no need to cache (already cached)):
        /// var f1 = WSN_NetCode.Expressions.ExpressionBasedReflection.GetInstanceCreatorForArrays1D();
        /// object tz2 = f1(typeof(string), 3);
        /// var setup = WSN_NetCode.Expressions.ExpressionBasedReflection.GetSetterForArraysValue1D(typeof(string));
        /// setup(tz2, 1, "kuku");
        /// </summary>
        /// <param name="arrayType"></param>
        /// <returns></returns>
        public static Action<object, int, object> GetValueSetterForArray1D()
        {
            if (_getValueSetterForArray1D != null)
                return _getValueSetterForArray1D;

            var instance = Expression.Parameter(typeof(object));
            var argument1 = Expression.Parameter(typeof(int));
            var value = Expression.Parameter(typeof(object));

            System.Reflection.MethodInfo mi = new List<System.Reflection.MethodInfo>
              (typeof(Array).GetMethods())
              .Where(r => r.ToString().Equals("Void SetValue(System.Object, Int32)")).FirstOrDefault();

            var setterCall = Expression.Call(
                Expression.Convert(instance, typeof(Array)),
                mi,
                value, argument1
                //Expression.Convert(value, arrayType), argument1
                );
            _getValueSetterForArray1D = (Action<object, int, object>)Expression.Lambda(setterCall, instance, argument1, value).Compile();
            return _getValueSetterForArray1D;
        }

    }
}



/*
 * Real code is faster than ref to Expression tree calls, but ETCalls are good as Reflection substitute for dynamic language constructions creation.
 *      //calling instantiated overlaoded methods
 
            StringBuilder sb = new StringBuilder();

            var instance = Expression.Parameter(typeof(StringBuilder));
            var arg1 = Expression.Parameter(typeof(string));

            Expression callExpr = Expression.Call(
            instance,
            typeof(StringBuilder).GetMethod("Append", new Type[] { typeof(String) }),
            arg1
            );

            var ec = Expression.Lambda<Action<StringBuilder, string>>(callExpr, instance, arg1).Compile();

            ec(sb,"dsfds");
            Console.WriteLine(sb.ToString());
 * 
 * 
 * //Calling static methods
 *             //    var arg1 = Expression.Parameter(typeof(string));
            //    Expression callExpr = Expression.Call(
            //                   typeof(WOW).GetMethod("Append"),
            //                   arg1
            //        //Expression.Constant(2)
            //               );


            //    //Action<string> sd = Expression.Lambda<Action<string>>(callExpr, arg1).Compile();
            //    sdstat = Expression.Lambda<Action<string>>(callExpr, arg1).Compile();
 
 */